﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;

namespace ECommerce.Web
{
    public class ModelBinder : DefaultModelBinder
    {
        private static Func<string, string, string> DefaultInvalidValueConversionFormatter =
            (attemptedValue, displayName) => String.Format("The value '{0}' is not valid for the '{1}' field.", attemptedValue, displayName);
        
        private Func<string, string, string> invalidValueConversionFormatter;

        public Func<string, string, string> InvalidValueConversionFormatter
        {
            get { return this.invalidValueConversionFormatter ?? DefaultInvalidValueConversionFormatter; }
            set { this.invalidValueConversionFormatter = value; }
        }

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);

            ModelState modelState = GetModelState(bindingContext, propertyDescriptor);

            if (modelState == null) { return; }

            AddFormatConversionErrors(modelState, propertyDescriptor);
        }

        private ModelState GetModelState(ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            string propertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);

            return bindingContext.ModelState[propertyKey];
        }

        private void AddFormatConversionErrors(ModelState modelState, PropertyDescriptor propertyDescriptor)
        {
            var errors = modelState.Errors.Where(x => x.ErrorMessage == String.Empty && x.Exception != null).ToList();

            foreach (var error in errors)
            {
                if (ErrorWasCausedByInvalidFormatConversion(error))
                {
                    modelState.Errors.Remove(error);

                    AddFormatConversionError(modelState, propertyDescriptor);

                    break;
                }
            }
        }

        private bool ErrorWasCausedByInvalidFormatConversion(ModelError error)
        {
            for (var exception = error.Exception; exception != null; exception = exception.InnerException)
            {
                if (exception is FormatException) { return true; }
            }

            return false;
        }

        private void AddFormatConversionError(ModelState modelState, PropertyDescriptor propertyDescriptor)
        {
            string propertyDisplayName = GetPropertyDisplayName(propertyDescriptor);
            string errorMessage = GetInvalidFormatConversionErrorMessage(modelState.Value.AttemptedValue, propertyDisplayName);

            modelState.Errors.Add(errorMessage);
        }

        private string GetPropertyDisplayName(PropertyDescriptor descriptor)
        {            
            var displayNameAttribute = descriptor.Attributes.OfType<DisplayNameAttribute>().FirstOrDefault();

            if (displayNameAttribute != null && !String.IsNullOrEmpty(displayNameAttribute.DisplayName))
            {
                return displayNameAttribute.DisplayName;
            }

            return descriptor.Name;
        }

        private string GetInvalidFormatConversionErrorMessage(string attemptedValue, string displayName)
        {
            return InvalidValueConversionFormatter(attemptedValue, displayName);
        }
    }
}
